package org.schat.sockets;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArraySet;

import javax.websocket.EncodeException;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;

import org.schat.sockets.handlers.IMessageHandler;
import org.schat.sockets.handlers.JoinHandler;
import org.schat.sockets.handlers.LeaveHandler;
import org.schat.sockets.handlers.RelayHandler;

@ServerEndpoint(value = "/websocket/chat", encoders = MessageEncoder.class, decoders = MessageDecoder.class)
public class ChatSocket {

	private static final ConcurrentMap<String, Set<ChatSocket>> connections = new ConcurrentHashMap<String, Set<ChatSocket>>();
	private static final Map<String, IMessageHandler> handlers;
	private Session session;
	private String room;
	private String name;
	private String avatarClass;
	private long joinTime;

	static {
		handlers = new HashMap<String, IMessageHandler>();
		ChatSocket.handlers.put(ChatMessage.JOIN_TYPE, new JoinHandler());
		ChatSocket.handlers.put(ChatMessage.LEAVE_TYPE, new LeaveHandler());
		ChatSocket.handlers.put(ChatMessage.CHAT_TYPE, new RelayHandler());
	}

	public void init(String room, String name, String avatarClass, long joinTime) {
		this.room = room;
		this.name = name;
		this.avatarClass = avatarClass;
		this.joinTime = joinTime;
	}

	public Set<ChatSocket> getRoom() {
		Set<ChatSocket> roomSet = ChatSocket.connections.putIfAbsent(this.room, new CopyOnWriteArraySet<ChatSocket>());
		if (roomSet == null) {
			roomSet = ChatSocket.connections.get(this.room);
		}
		return roomSet;
	}

	public void leaveRoom() {
		Set<ChatSocket> roomSet = getRoom();
		if (roomSet != null) {
			if (roomSet.remove(this)) {
				this.send(new ChatMessage(ChatMessage.LEAVE_TYPE, ""));
			}
		}
	}

	@OnOpen
	public void start(Session session) {
		this.session = session;
	}

	@OnClose
	public void onClose() {
		System.out.println("onClose");
		leaveRoom();
		this.session = null;
	}

	@OnMessage
	public void onMessage(ChatMessage message) {
		if (message != null) {
			if (ChatSocket.handlers.containsKey(message.getType())) {
				IMessageHandler handler = ChatSocket.handlers.get(message.getType());
				handler.handle(this, message);
			}
		}
	}

	@OnError
	public void onError(Throwable t) {
		System.out.println("onError");
		t.printStackTrace();
	}

	public void send(ChatMessage message) {
		ChatSocket.broadcast(this, message.setName(this.name));
	}

	public static void sendUsers(ChatSocket socket) {
		Set<ChatSocket> peers = ChatSocket.connections.get(socket.room);
		for (ChatSocket user : peers) {
			if (!user.name.equalsIgnoreCase(socket.name)) {
				try {
					socket.session.getBasicRemote().sendObject(
							(new ChatMessage(ChatMessage.JOIN_TYPE, "")).setName(user.name).setAvatarClass(user.avatarClass).setJoinTime(user.joinTime));
				} catch (IOException | EncodeException | NullPointerException e) {
					System.out.println("broadcast: " + e.getMessage());
					e.printStackTrace();
				}
			}
		}
	}

	private static void broadcast(ChatSocket socket, ChatMessage message) {
		Set<ChatSocket> peers = ChatSocket.connections.get(socket.room);
		for (ChatSocket user : peers) {
			try {
				user.session.getBasicRemote().sendObject(message);
			} catch (IOException | EncodeException | NullPointerException e) {
				System.out.println("broadcast: " + e.getMessage());
				e.printStackTrace();
			}
		}
	}

}
